package main

import (
	"context"
	"golang.org/x/sync/errgroup"
	"io"
	"net/http"
	"os"
	"os/signal"
)

// Server http 服务。
type Server struct {
	srv *http.Server
}

// NewServer 创建一个 http 服务。
func NewServer() *Server {
	s := &Server{}
	s.srv = &http.Server{Addr: ":8080"}
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		_, _ = io.WriteString(w, "hello world\n")
	})
	return s
}

// Start 启动 http 服务。
func (s *Server) Start() error {
	return s.srv.ListenAndServe()
}

// Shutdown 停止 http 服务。
func (s *Server) Shutdown(ctx context.Context) error {
	return s.srv.Shutdown(ctx)
}

func main() {
	// 创建一个 errgroup
	g, ctx := errgroup.WithContext(context.Background())

	// http server
	g.Go(func() error {
		// 创建一个 http server
		svr := NewServer()
		// 如果 errgroup 出现错误，终止服务，需要使用协程，否则启动服务会挂起，导致无法调用退出程序
		// 通过调用 Shutdown 函数来使 Start 函数报错退出
		go func() {
			<-ctx.Done()
			_ = svr.Shutdown(context.TODO())
		}()
		// 启动服务
		return svr.Start()
	})

	// signal
	g.Go(func() error {
		// 使用带 buffer 的通道，防止错过信号
		c := make(chan os.Signal, 1)
		// 监听和捕获中断信号
		signal.Notify(c, os.Interrupt)
		// 通过 for select 监听 errgroup 是否有错误或者捕获到信号
		for {
			select {
			case <-ctx.Done():
				// context 结束，退出函数
				return ctx.Err()
			case <-c:
				// 捕获到目标信号，可以做一些业务逻辑
				// 如果需要退出 http server 则在此返回一个错误
				return nil
			}
		}
	})

	// 等待 group 中所有函数结束，如果出现错误，则返回第一个错误
	_ = g.Wait()
}
